Skip to content

Add mock generation for @Instantiable types#204

Draft
dfed wants to merge 120 commits intodfed/root-scannerfrom
dfed--mock-gen
Draft

Add mock generation for @Instantiable types#204
dfed wants to merge 120 commits intodfed/root-scannerfrom
dfed--mock-gen

Conversation

@dfed
Copy link
Copy Markdown
Owner

@dfed dfed commented Apr 1, 2026

Summary

SafeDI now automatically generates mock() methods for @Instantiable types, with full dependency tree support including Instantiator<T>, erasedToConcreteExistential, and @Forwarded properties.

Core mock generation

  • Mock generation: Each @Instantiable type gets a mock() static method that builds its full dependency subtree inline, with every node overridable via optional closure parameters
  • SafeDIMockPath enums: Nested enums per dependency type describe where each dependency is created in the tree (case root, case childA, etc.)
  • Instantiator<T> support: Mock wraps inline tree in Instantiator { forwarded in ... } closure. Forwarded properties become closure parameters. Transitive deps flow through from parent scope.
  • erasedToConcreteExistential support: Auto-wraps concrete types in erased wrappers (e.g., AnyUserService(DefaultUserService.mock()))
  • @SafeDIConfiguration properties: generateMocks: Bool and mockConditionalCompilation: StaticString?
  • @Instantiable parameter: mockAttributes: StaticString for global actor annotations (e.g., @MainActor)
  • Per-module mock generation: Each module with SafeDIGenerator plugin generates mocks for its own types. Mock generation defaults to false when no @SafeDIConfiguration exists.

User-defined mock() methods

  • When a type declares static func mock(...) in its @Instantiable body, SafeDI skips generating a mock file for that type
  • Parent types constructing the child call Child.mock(...) instead of Child(...), threading parameters through the user's custom method
  • The @Instantiable macro validates that mock methods are public, static/class, and include parameters for all dependencies (with fix-its for missing parameters)
  • No-arg mock() methods use .mock() for construction — default-valued init params do not bubble through

Default-valued init parameters

  • Init parameters with default values that aren't SafeDI dependencies (e.g., flag: Bool = false, viewModel: VM? = nil) are exposed as optional closure parameters on the root mock
  • Default-valued params bubble transitively through the tree — a grandchild's default appears at the root mock level
  • Bubbling stops at Instantiator/SendableInstantiator/ErasedInstantiator/SendableErasedInstantiator boundaries and at types with user-defined mock() methods
  • Bindings use if let x = override { x } else { default } to preserve @MainActor/@Sendable type context on closure defaults
  • @escaping is stripped from default-valued closure types (invalid in return position); other attributes (@Sendable, @MainActor) are preserved
  • When a user-defined mock() doesn't fulfill all dependencies, the generated code emits a /* @Instantiable type is incorrectly configured */ comment (same pattern as production code gen), triggering a build error that directs users to the macro fix-it

Example project updates

  • Example Package Integration: Plugin added to all targets. Mocks generated for all modules.
  • ExampleProjectIntegration: #Preview blocks updated to use .mock() with zero manual construction
  • ExampleMultiProjectIntegration: Subproject framework target now has SafeDIGenerator plugin and @SafeDIConfiguration for per-module mock generation. #Preview blocks use .mock().
  • ExamplePrebuiltPackageIntegration: Plugin added to all targets.

Testing

  • 507 tests total (100+ mock generation tests + existing)
  • 100% line coverage on ScopeGenerator.swift and Initializer.swift
  • All tests use exact full-output == comparison (no contains assertions)
  • Coverage includes: Instantiator<T> with/without forwarded properties, erasedToConcreteExistential, extension-based types, deep nesting, multiple branches, protocol fulfillment, lazy self-instantiation cycles, default-valued arguments (Bool, String, Int, closures, Optional, complex expressions), @MainActor/@Sendable closure defaults, user-defined mock methods (with/without params), disambiguation, instantiator boundaries, grandchild bubbling, misconfigured mock error comments, and more

Test plan

  • 507 tests pass (swift test)
  • 100% line coverage on new code (swift test --enable-code-coverage)
  • Example Package Integration builds (swift build)
  • ExampleProjectIntegration builds (xcrun xcodebuild)
  • ExampleMultiProjectIntegration builds (xcrun xcodebuild)
  • Lint clean (swiftformat)
  • CI passes

🤖 Generated with Claude Code

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 1, 2026

Codecov Report

❌ Patch coverage is 99.92361% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.89%. Comparing base (acf381f) to head (a58052d).

Files with missing lines Patch % Lines
Sources/SafeDICore/Models/TypeDescription.swift 91.66% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                  Coverage Diff                  @@
##           dfed/root-scanner     #204      +/-   ##
=====================================================
- Coverage              99.92%   99.89%   -0.03%     
=====================================================
  Files                     40       40              
  Lines                   3809     4924    +1115     
=====================================================
+ Hits                    3806     4919    +1113     
- Misses                     3        5       +2     
Files with missing lines Coverage Δ
...s/SafeDICore/Errors/FixableInstantiableError.swift 100.00% <100.00%> (ø)
...ICore/Errors/FixableSafeDIConfigurationError.swift 100.00% <100.00%> (ø)
...eDICore/Extensions/AttributeSyntaxExtensions.swift 100.00% <100.00%> (ø)
...afeDICore/Generators/DependencyTreeGenerator.swift 100.00% <100.00%> (ø)
Sources/SafeDICore/Generators/ScopeGenerator.swift 100.00% <100.00%> (ø)
Sources/SafeDICore/Models/Initializer.swift 100.00% <100.00%> (ø)
Sources/SafeDICore/Models/InstantiableStruct.swift 100.00% <100.00%> (ø)
...ources/SafeDICore/Models/SafeDIConfiguration.swift 100.00% <100.00%> (ø)
Sources/SafeDICore/Models/SafeDIToolManifest.swift 100.00% <100.00%> (ø)
Sources/SafeDICore/Models/Scope.swift 100.00% <100.00%> (ø)
... and 7 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dfed dfed force-pushed the dfed--mock-gen branch from 53f2e08 to 1490e8f Compare April 2, 2026 16:52
dfed and others added 27 commits April 2, 2026 10:51
SafeDI now automatically generates `mock()` methods for every `@Instantiable`
type, building full dependency subtrees with overridable closure parameters.

New @SafeDIConfiguration properties:
- `generateMocks: Bool` (default true) — controls mock generation
- `mockConditionalCompilation: StaticString?` (default "DEBUG") — #if wrapping

New @INSTANTIABLE parameter:
- `mockAttributes: StaticString` — attributes for generated mock() (e.g. "@mainactor")

Each mock gets a `SafeDIMockPath` enum with nested enums per dependency type,
enabling callers to differentiate same-type dependencies at different tree paths.

The build plugin now generates mock output files alongside DI tree files.
Multi-module projects can add the plugin to all targets for per-module mocks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Deduplicate mock generation for types with fulfillingAdditionalTypes
- Add generateMocks/mockConditionalCompilation to ExampleMultiProjectIntegration config
- Fix swiftformat lint issues in MockGenerator
- Scope mock generation to target files to avoid multi-module duplicates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mock generation for Instantiator<T> and erasedToConcreteExistential types
is not yet supported. Disable mocks in Xcode project examples (which use
these features) while the SPM package examples work correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Xcode project needs the config file in its project.pbxproj to
pick up generateMocks: false.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove unreachable isExtension branch from generateSimpleMock
and unused defaultConstruction method.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…types

- Rewrite SafeDIToolMockGenerationTests to use full output comparison
  (no `contains`), matching SafeDIToolCodeGenerationTests style
- Add #Preview blocks using .mock() to views in both Xcode example projects
- Re-enable generateMocks for Xcode example projects
- MockGenerator: skip types with Instantiator deps (not yet supported)
- MockGenerator: make params required (no default) for types not in type map
- Track hasKnownMock per type entry for required vs optional params
- Add test for extension-based type with nil conditional compilation
- Add test for required parameter when type not in type map

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Types not in the type map (like AnyUserService) now get non-optional
closure parameters (@escaping, no `?`) instead of optional closures
with a broken default. This ensures the generated code compiles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For types instantiated with `erasedToConcreteExistential: true`, the mock
now generates TWO parameters: one for the concrete type (DefaultMyService)
and one for the erased wrapper (AnyMyService). The erased type's default
wraps the concrete type: `AnyMyService(defaultMyService)`.

Non-@INSTANTIABLE types now use non-optional @escaping closure parameters
instead of optional closures, avoiding broken defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… coverage

- MockGenerator now detects erasedToConcreteExistential relationships globally
  and auto-generates wrapping for received types (e.g., AnyUserService wraps
  DefaultUserService.mock())
- #Preview blocks simplified to NameEntryView.mock() and NoteView.mock(userName:)
- Consolidated duplicate arg-matching branches in buildInlineConstruction
- Added hasReceivedDepsInScope check for sourceType form
- Added test for received erased type auto-wrapping
- Added test for complex mock with nil conditional compilation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For received erased types (like @received AnyUserService), the mock now
has only the erased type as a parameter — the concrete type
(DefaultUserService) is built inline in the default construction:

    AnyUserService(DefaultUserService.mock())

For @Instantiated(erasedToConcreteExistential: true) at the root level,
both the concrete and erased type remain parameters, with the erased
type referencing the concrete variable.

#Preview blocks simplified to zero manual construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Multiple branches receiving the same @received property
- Protocol type fulfilled by fulfillingAdditionalTypes
- Multiple roots each getting their own mock file
- Construction ordering respects @received dependencies
- Four-level deep tree with shared leaf threading

19 mock generation tests total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ExampleMultiProjectIntegration uses additionalDirectoriesToInclude
for Subproject files. The Xcode plugin only scans target.inputFiles for
mock entries, so Subproject types (DefaultUserService, UserDefaults)
don't get generated mocks. The single-project example keeps .mock()
#Previews since all its files are in the target.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mock generation does not yet support types from
additionalDirectoriesToInclude. The Xcode plugin only scans
target.inputFiles for mock entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add test for missing mockConditionalCompilation fix-it
- Add test for mockConditionalCompilation without initializer
- Add test for inline construction skipping default-valued arguments
- Add test for RootScanner.outputFiles computed property
- Remove unreachable defensive branches (force-unwrap known-good lookups)
- Extract wrapInConditionalCompilation helper to fix coverage instrumentation
- Simplify topological sort dependency check

413 tests, 0 uncovered new lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove hasUnsupportedDeps skip — all @INSTANTIABLE types now get mocks
- TypeEntry gains enumName, paramLabel, isInstantiator, builtTypeForwardedProperties
- Instantiator deps use property label as enum name
- Default wraps inline tree in Instantiator { forwarded in ... } closure
- Forwarded props become Instantiator closure parameters
- Topological sort handles Instantiator deps (wait for captured parent vars)
- No boundary — transitive deps inside Instantiator are parent mock params

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Test Instantiator<T> with forwarded properties (closure param)
- Test Instantiator<T> without forwarded properties
- Both verify enum naming, parameter types, and inline closure construction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no @SafeDIConfiguration is found, generateMocks now defaults to
false. Modules must explicitly opt in via @SafeDIConfiguration.

Added enableMockGeneration parameter to test helper and test for
the no-config default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Types included via additionalDirectoriesToInclude are not scanned for
mock generation. Document this limitation in the Manual and in the
ExampleMultiProjectIntegration SafeDIConfiguration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Test Instantiator with multiple forwarded properties (tuple destructuring)
- Remove unused parameterLabel(for:) method
- Update documentation: mock generation is per-module, not per-project

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Force unwraps are not the pattern in this codebase. Restore proper
guard/else fallbacks in buildInlineConstruction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extension-based type with received deps (inline .instantiate())
- Extension-based type as inline construction target
- Instantiator with default-valued built type argument

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SafeDIGenerator plugin dependency to Subproject Xcode framework target
- Add @SafeDIConfiguration to Subproject with generateMocks: true
- Re-enable generateMocks on main app target
- Update #Preview blocks to use .mock()
- Keep additionalDirectoriesToInclude for DI tree generation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All positive mock generation test assertions now use exact full-output
comparison. Only negative checks (!contains("extension")) remain for
tests verifying mocks are NOT generated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instantiator types always have hasKnownMock = true because the built
type must be @INSTANTIABLE (validated upstream). The else branch was
unreachable dead code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unreachable defensive branches (trust validated data, matching
  ScopeGenerator/DependencyTreeGenerator pattern of 0 uncovered lines)
- Simplify arg building to use nil-coalescing on optional initializer
- Keep extension type checks for .instantiate() calls
- Add lazy self-instantiation cycle test (exercises topo sort cycle breaker)

422 tests, 0 uncovered lines in MockGenerator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dfed and others added 11 commits April 2, 2026 10:51
Forwarded closure parameters (e.g., onDismiss: () -> Void) need @escaping
in the mock function signature since they're passed to an init that stores
them. Fixed by using asFunctionParameter (which adds @escaping for closure
types) instead of bare asSource for forwarded declaration source types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename `casesStr` to `casesString` (no abbreviations)
- Fix `dep` → `dependency` in comment
- Fix `param` → `parameter` and `params` → `parameters` in comments
- Rename test functions: `Dep` → `Dependency` in three test names
- Convert `if continue` patterns to `guard` in ScopeGenerator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Replace partial .contains() assertion in
   mock_passesNilForOnlyIfAvailableProtocolDependency with full output
   equality check, matching the convention of all other mock tests.

2. Fix latent bug in isOnlyIfAvailable check: use unwrapped Property
   identity (label + type) instead of just label when checking for
   Optional counterparts. This prevents false collisions when unrelated
   types share a label (e.g., `service: ConcreteService` aliased
   onlyIfAvailable coexisting with `service: ServiceProtocol?` Optional).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The boolean controls whether a mock parameter is optional (= nil) or
required (@escaping). The old name suggested something about mock
existence rather than parameter optionality. Added doc comment
explaining the three cases: known default construction, onlyIfAvailable,
and not constructible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix Manual.md: mock generation requires @SafeDIConfiguration (was
  incorrectly stated as enabled by default); fix path enum example to
  use actual generated case names (root, childA) not stale format
- Fix SafeDIConfiguration.swift doc: clarify generateMocks default is
  true only when @SafeDIConfiguration exists
- Remove duplicate doc-comment line in ScopeGenerator.swift
- Improve doc comments: isOptionalParameter, onlyIfAvailable set,
  alias guard explanation
- Rename queue → worklist and BFS → worklist in DependencyTreeGenerator
  (code uses popLast/stack semantics, not FIFO)
- Improve memoization cache comment
- Delete stale mock_audit_issues.md (all issues resolved)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move scope lookup from createMockRootScopeGenerator to caller —
  the caller's guard already has other conditions that cover the
  else branch, so no new uncoverable path is introduced
- Restructure childMockCodeGeneration to accept the child's label
  (String?) instead of extracting it from ScopeGenerator.property
  via a guard. Uses flatMap for the optional label lookup.
- Remove unreachable `guard !code.isEmpty` — generateMockRootCode
  always produces non-empty code (extension wrapper at minimum)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both cases return the same associated value. Merging them into a single
pattern eliminates the uncovered .root branch (only .property is reached
during mock generation, but .root is reached during production generation
— merging means both cover the same line).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a child type defines a `static func mock(...)` with parameters
matching its dependencies, the generated parent mock now calls
`Child.mock(dependency: dependency)` instead of `Child(dependency: ...)`.
This ensures user-defined mock behavior (e.g., preview setup, test
configuration) is preserved in the dependency tree.

- Replace `hasExistingMockMethod: Bool` with `mockInitializer: Initializer?`
  on Instantiable, parsing the mock function signature via the existing
  `Initializer(FunctionDeclSyntax)` constructor
- In mock code generation, use `.mock()` when the mock initializer has
  parameters (no-parameter mocks can't thread dependencies and fall back
  to the regular initializer)
- Types with user-defined mocks still skip mock FILE generation (no
  collision with the user's method)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gument

Store the actual default value expression text (e.g., "nil", ".init()")
instead of just a boolean. hasDefaultValue becomes a computed property
derived from defaultValueExpression != nil. This preserves the default
value source text needed for bubbling default-valued parameters up to
mock signatures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dfed dfed force-pushed the dfed--mock-gen branch from 9ed60a5 to 097326d Compare April 2, 2026 17:53
dfed and others added 18 commits April 2, 2026 10:55
Types with user-defined mock methods are skipped in generateMockCode
(DependencyTreeGenerator guard), so the root-level construction code
never sees a mockInitializer. The .mock() construction only applies at
the child level (in generatePropertyCode). Removed the dead branches
from both simple-mock and complex-mock root paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change 1 completion:
- Add macro validation: mock() must be public (with fix-it to add
  modifier). Argument validation for dependencies requires the full
  tool pipeline (macro expansion strips decorators before visitor runs).
- Fix extension-based types: mockInitializer was nil when instantiate()
  appeared before mock() in source order. The instantiables getter now
  patches mockInitializer onto extension instantiables after all visits.
- Add tests: deep tree with user mock, extension-based type with user
  mock, reversed source order for extension, non-public mock diagnostic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- mock_existingMockMethodWithMultipleDependencies: verifies all
  dependency parameters are threaded to Child.mock()
- mock_existingMockMethodSkipsGenerationForTypeButGeneratesForParent:
  verifies child gets no generated mock while parent gets SafeDIMockPath
  enum and calls Child.mock() — uses full output equality assertions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n, var mockInitializer

- Extract buildFixedParameterClause helper in InstantiableMacro to
  deduplicate parameter-list fix-it logic between init and mock validation
- Make instantiationDeclaration conditional on codeGeneration mode
  (avoids computing mock declaration string in dependency tree mode)
- Make mockInitializer a var on Instantiable so the extension visitor
  can patch it with a simple mutation instead of rebuilding the struct

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default-valued init parameters (e.g., `flag: Bool = false`) now bubble up
to the root mock method as optional closure parameters. Users can override
them at test time or let them fall back to the original default expression.

Key changes:
- Add createMockInitializerArgumentList to Initializer (includes all args)
- Add defaultValueExpression to MockDeclaration for tracking defaults
- collectMockDeclarations collects default-valued args from constant children
- generatePropertyCode wraps default-arg bindings in scoped functions
- generateMockRootCode handles root's own default-valued args
- Default-valued params do NOT bubble through Instantiator boundaries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests for disambiguation, all instantiator boundaries, complex defaults,
user-defined mock + defaults, multi-level bubbling, and grandchild
stopped at instantiator boundary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers the unavailable-property nil pass-through and unexpected
non-default argument error paths. Achieves 100% line coverage on
both Initializer.swift and ScopeGenerator.swift.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expands the mock generation section of Manual.md to cover:
- User-defined mock() methods being called by parent mocks
- Macro validation requirements for user-defined mocks
- Default-valued init parameters bubbling up to root mocks
- Instantiator boundaries that stop bubbling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@escaping is only valid on function parameters, not in closure return
type positions. When a default-valued init parameter has type
@escaping () -> Void, the generated mock parameter would emit
(SafeDIMockPath.X) -> @escaping () -> Void which is invalid Swift.

Fix: add TypeDescription.strippingEscaping and use it when building
MockDeclarations for default-valued arguments. Also fixes the enum
name (was escapingVoid_to_Void, now Void_to_Void).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The compiler cannot always infer @Sendable/@mainactor attributes on
closure literals from the ?? nil-coalescing context. Adding explicit
type annotations (e.g., `let onComplete: @sendable () -> Void = ...`)
ensures attributed closure types are preserved through the binding.

Also adds tests for @mainactor and @sendable closure default parameters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a child type has a user-defined static func mock() — even with
no parameters — use .mock() for construction instead of falling back
to the regular init. This prevents default-valued init params from
incorrectly bubbling through types that handle their own test
construction. Fixes @Sendable/@mainactor closure type mismatch in
generated mocks for types like DelayedBackgroundTaskService.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ?? operator doesn't propagate type context (@mainactor, @sendable)
to closure literals on the RHS. Using `if let x = override { x } else
{ defaultExpr }` gives each branch proper type inference from the
explicit binding type annotation, so @MainActor/@sendable closure
defaults compile correctly in the generated mock.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a user-defined mock() doesn't fulfill all dependencies, emit the
same /* incorrectly configured */ comment used in production code gen.
This triggers a build error directing the user to the @INSTANTIABLE
macro fix-it, matching the production pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default-valued parameter declarations from children were included in
updatedCoveredLabels, causing received properties with the same label
to be skipped from the uncovered list. This left root-level code
referencing the raw closure parameter instead of a resolved value.

Fix: exclude declarations with defaultValueExpression from the
coverage check so received properties always get root-level bindings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add mockMethodMissingDependencyProducesDiagnostic macro test
- Add mockMethodMissingMultipleDependencies macro test with fix-it
- Add mockMethodWithPartialDeps test covering buildFixedParameterClause
- Add defaultValuedParamDoesNotSuppressReceivedPropertyBinding test
- Fix default-valued params suppressing received property bindings
- Remove incorrect comment about macro tests being impossible
- FixableInstantiableError.swift now at 100% coverage
- InstantiableMacro.swift from 57 to 4 uncovered lines (dead code only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The else branch for no-modifier mock functions was unreachable (mock
detection requires static/class). The .inaccessibleInitializer switch
case was handled by the isPublicOrOpen check above. Simplified both
to eliminate uncoverable lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
generateMockCode now accepts currentModuleSourceFilePaths to filter
out types from dependent modules. Previously, mocks were generated
for ALL types in the tree (including dependent modules) and then
discarded when they didn't match a manifest entry. Now they're
skipped upfront, avoiding wasted scope tree construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TypeDescription.strippingEscaping: test specifiers-only path
  (escaping removed, borrowing specifier preserved)
- FixableInstantiableError: test descriptions and fix-it messages
  for mockMethodMissingArguments and mockMethodNotPublic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant